"""
Simplified configuration management for GCR Solver Manager.

This module provides a lightweight YAML-based configuration system
without heavy dependencies like Pydantic.
"""

import os
import yaml
import logging
from pathlib import Path
from typing import Dict, Any, List, Optional, Union

logger = logging.getLogger(__name__)


class SimpleConfig:
    """Simple YAML-based configuration manager."""
    
    def __init__(self, config_data: Optional[Dict[str, Any]] = None):
        """Initialize configuration with optional data."""
        self.data = config_data or {}
        self._setup_defaults()
    
    def _setup_defaults(self):
        """Setup default configuration values."""
        # Auto-detect gcr-nccl directory
        gcr_dir = self._find_gcr_nccl_dir()
        
        defaults = {
            # Database settings
            'database': {
                'path': str(Path.home() / '.cache' / 'gsm' / 'gcr_logs.db'),
                'backup_enabled': True,
                'cleanup_days': 90
            },
            
            # Build settings
            'build': {
                'source_dir': str(gcr_dir) if gcr_dir else '.',
                'parallel_jobs': 4,
                'timeout_seconds': 1800,
                'default_gpu': 'cuda'
            },
            
            # Test settings
            'test': {
                'default_nproc': 4,
                'default_resolution': 1.0,
                'timeout_seconds': 3600,
                'slurm_enabled': True,
                'slurm_partition': 'gpu'
            },
            
            # Remote execution settings
            'remote': {
                'enabled': False,
                'hostname': None,
                'workdir': None,
                'sync_enabled': True,
                'git_remote_branch': 'nccl',
                'git_local_branch': 'nccl', 
                'git_remote_name': 'origin',
                'git_local_name': 'local'
            },
            
            # Log settings
            'logs': {
                'directory': 'logs',
                'retention_days': 30,
                'max_size_mb': 100
            },
            
            # Analysis settings
            'analysis': {
                'norm_tolerance': 1e-10,
                'comparison_enabled': True,
                'report_dir': 'reports'
            },
            
            # System settings
            'system': {
                'log_level': 'INFO',
                'debug': False,
                'temp_dir': '/tmp/gsm'
            }
        }
        
        # Merge defaults with existing data
        for section, values in defaults.items():
            if section not in self.data:
                self.data[section] = {}
            for key, default_value in values.items():
                if key not in self.data[section]:
                    self.data[section][key] = default_value
    
    def get(self, key_path: str, default: Any = None) -> Any:
        """
        Get configuration value using dot notation.
        
        Args:
            key_path: Dot-separated path to configuration value (e.g., 'database.path')
            default: Default value if key not found
            
        Returns:
            Configuration value or default
        """
        keys = key_path.split('.')
        current = self.data
        
        try:
            for key in keys:
                current = current[key]
            return current
        except (KeyError, TypeError):
            return default
    
    def set(self, key_path: str, value: Any) -> None:
        """
        Set configuration value using dot notation.
        
        Args:
            key_path: Dot-separated path to configuration value
            value: Value to set
        """
        keys = key_path.split('.')
        current = self.data
        
        # Navigate to parent of final key
        for key in keys[:-1]:
            if key not in current:
                current[key] = {}
            current = current[key]
        
        # Set final value
        current[keys[-1]] = value
    
    def get_section(self, section: str) -> Dict[str, Any]:
        """
        Get entire configuration section.
        
        Args:
            section: Section name
            
        Returns:
            Section configuration dictionary
        """
        return self.data.get(section, {})
    
    def update_section(self, section: str, values: Dict[str, Any]) -> None:
        """
        Update configuration section with new values.
        
        Args:
            section: Section name
            values: Dictionary of values to update
        """
        if section not in self.data:
            self.data[section] = {}
        self.data[section].update(values)
    
    def load_from_file(self, config_path: Union[str, Path]) -> None:
        """
        Load configuration from YAML file.
        
        Args:
            config_path: Path to YAML configuration file
        """
        config_path = Path(config_path)
        
        if not config_path.exists():
            logger.warning(f"Configuration file not found: {config_path}")
            return
        
        try:
            with open(config_path, 'r') as f:
                file_data = yaml.safe_load(f) or {}
            
            # Merge file data with existing data
            self._merge_data(file_data)
            logger.info(f"Configuration loaded from {config_path}")
            
        except yaml.YAMLError as e:
            logger.error(f"Failed to parse YAML configuration: {e}")
            raise
        except Exception as e:
            logger.error(f"Failed to load configuration file: {e}")
            raise
    
    def save_to_file(self, config_path: Union[str, Path]) -> None:
        """
        Save configuration to YAML file.
        
        Args:
            config_path: Path to save configuration
        """
        config_path = Path(config_path)
        config_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            with open(config_path, 'w') as f:
                yaml.dump(self.data, f, default_flow_style=False, sort_keys=False)
            logger.info(f"Configuration saved to {config_path}")
            
        except Exception as e:
            logger.error(f"Failed to save configuration file: {e}")
            raise
    
    def _merge_data(self, new_data: Dict[str, Any]) -> None:
        """Recursively merge new data into existing configuration."""
        def merge_dict(base: Dict[str, Any], update: Dict[str, Any]) -> None:
            for key, value in update.items():
                if key in base and isinstance(base[key], dict) and isinstance(value, dict):
                    merge_dict(base[key], value)
                else:
                    base[key] = value
        
        merge_dict(self.data, new_data)
    
    def load_env_file(self, env_file_path: Optional[Union[str, Path]] = None) -> None:
        """
        Load environment variables from .env file.
        
        Args:
            env_file_path: Path to .env file. If None, looks for .env in current directory and gcr-solver-manager directory
        """
        if env_file_path is None:
            # Try current directory first
            env_file_path = Path('.env')
            if not env_file_path.exists():
                # Try gcr-solver-manager package directory as fallback
                # This points to the directory containing the gcr-solver-manager package
                package_root = Path(__file__).parent.parent.parent.parent
                fallback_path = package_root / '.env'
                if fallback_path.exists():
                    env_file_path = fallback_path
                    logger.info(f"Using .env file from gcr-solver-manager package directory: {env_file_path}")
                else:
                    logger.debug(f".env file not found in current directory or gcr-solver-manager package directory")
                    return
        else:
            env_file_path = Path(env_file_path)
        
        if not env_file_path.exists():
            logger.debug(f".env file not found: {env_file_path}")
            return
        
        try:
            with open(env_file_path, 'r') as f:
                for line_num, line in enumerate(f, 1):
                    line = line.strip()
                    
                    # Skip empty lines and comments
                    if not line or line.startswith('#'):
                        continue
                    
                    # Parse KEY=value format
                    if '=' not in line:
                        logger.warning(f"Invalid .env line {line_num}: {line}")
                        continue
                    
                    key, value = line.split('=', 1)
                    key = key.strip()
                    value = value.strip()
                    
                    # Remove quotes if present
                    if (value.startswith('"') and value.endswith('"')) or \
                       (value.startswith("'") and value.endswith("'")):
                        value = value[1:-1]
                    
                    # Map specific SSH/remote variables to configuration
                    if key == 'SSH_HOSTNAME':
                        self.set('remote.hostname', value)
                    elif key == 'REMOTE_WORKDIR':
                        self.set('remote.workdir', value)
                    elif key == 'GIT_REMOTE_BRANCH':
                        self.set('remote.git_remote_branch', value)
                    elif key == 'GIT_LOCAL_BRANCH':
                        self.set('remote.git_local_branch', value)
                    elif key == 'GIT_REMOTE_NAME':
                        self.set('remote.git_remote_name', value)
                    elif key == 'GIT_LOCAL_NAME':
                        self.set('remote.git_local_name', value)
                    else:
                        # Set as environment variable for other uses
                        os.environ[key] = value
                    
                    logger.debug(f"Loaded .env variable: {key}")
            
            # Enable remote functionality if SSH hostname is available
            if self.get('remote.hostname'):
                self.set('remote.enabled', True)
                logger.info(f"Remote configuration loaded from {env_file_path}")
            
        except Exception as e:
            logger.error(f"Failed to load .env file {env_file_path}: {e}")

    def apply_environment_overrides(self) -> None:
        """
        Apply environment variable overrides.
        
        Environment variables prefixed with GSM_ will override configuration.
        Format: GSM_SECTION_KEY (e.g., GSM_DATABASE_PATH)
        """
        prefix = "GSM_"
        
        for env_var, value in os.environ.items():
            if not env_var.startswith(prefix):
                continue
            
            # Parse environment variable name
            config_key = env_var[len(prefix):].lower()
            if '_' in config_key:
                parts = config_key.split('_', 1)
                section, key = parts[0], parts[1]
                
                # Convert value to appropriate type
                converted_value = self._convert_env_value(value)
                
                # Set in configuration
                if section not in self.data:
                    self.data[section] = {}
                self.data[section][key] = converted_value
                
                logger.debug(f"Environment override: {section}.{key} = {converted_value}")
    
    def _convert_env_value(self, value: str) -> Any:
        """Convert environment variable string to appropriate type."""
        # Handle boolean values
        if value.lower() in ('true', 'false'):
            return value.lower() == 'true'
        
        # Handle integer values
        if value.isdigit() or (value.startswith('-') and value[1:].isdigit()):
            return int(value)
        
        # Handle float values
        try:
            if '.' in value:
                return float(value)
        except ValueError:
            pass
        
        # Handle None/null values
        if value.lower() in ('none', 'null', ''):
            return None
        
        # Return as string
        return value
    
    def get_absolute_path(self, relative_path: str, base_path: Optional[str] = None) -> Path:
        """
        Convert relative path to absolute path.
        
        Args:
            relative_path: Relative path string
            base_path: Base path for relative resolution (defaults to current dir)
            
        Returns:
            Absolute Path object
        """
        path = Path(relative_path)
        if path.is_absolute():
            return path
        
        if base_path:
            return Path(base_path).resolve() / relative_path
        else:
            return Path.cwd() / relative_path
    
    def _find_gcr_nccl_dir(self) -> Optional[Path]:
        """
        Automatically find the gcr-nccl directory containing scripts/build.sh.
        
        Returns:
            Path to gcr-nccl directory or None if not found
        """
        # List of potential locations to check
        candidates = []
        
        # 1. Environment variable
        env_path = os.environ.get("GSM_GCR_NCCL_PATH")
        if env_path:
            candidates.append(Path(env_path))
        
        # 2. Current working directory and parents
        current = Path.cwd().resolve()
        for path in [current] + list(current.parents):
            candidates.append(path)
            candidates.append(path / "gcr-nccl")
        
        # 3. Package installation directory
        package_dir = Path(__file__).parent.parent.parent.parent
        candidates.append(package_dir)
        candidates.append(package_dir.parent / "gcr-nccl")
        
        # 4. Common installation paths
        common_paths = [
            Path.home() / "code" / "work" / "Helmholtz" / "gcr-nccl",
            Path("/home/linden/code/work/Helmholtz/gcr-nccl"),
            Path("/workspace/gcr-nccl"),
            Path("/opt/gcr-nccl"),
        ]
        candidates.extend(common_paths)
        
        # Check each candidate
        for candidate in candidates:
            if candidate.exists() and (candidate / "scripts" / "build.sh").exists():
                logger.info(f"Found gcr-nccl directory: {candidate}")
                return candidate
        
        logger.warning("Could not auto-detect gcr-nccl directory")
        return None

    def validate(self) -> List[str]:
        """
        Validate configuration and return list of errors.
        
        Returns:
            List of validation error messages
        """
        errors = []
        
        # Validate GPU type
        gpu_type = self.get('build.default_gpu')
        if gpu_type not in ['cuda', 'hip', 'cpu']:
            errors.append(f"Invalid GPU type: {gpu_type}. Must be 'cuda', 'hip', or 'cpu'")
        
        # Validate log level
        log_level = self.get('system.log_level', 'INFO')
        valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
        if log_level.upper() not in valid_levels:
            errors.append(f"Invalid log level: {log_level}. Must be one of {valid_levels}")
        
        # Validate paths - auto-detect if not set
        source_dir = self.get('build.source_dir')
        if not source_dir or source_dir == "." or not Path(source_dir).exists():
            # Auto-detect the gcr-nccl directory
            gcr_dir = self._find_gcr_nccl_dir()
            if gcr_dir:
                self.data['build']['source_dir'] = str(gcr_dir)
            else:
                errors.append("Could not auto-detect gcr-nccl directory and source_dir not configured")
        
        # Validate numeric values
        numeric_checks = [
            ('build.parallel_jobs', 1, 64),
            ('build.timeout_seconds', 60, 7200),
            ('test.default_nproc', 1, 1024),
            ('test.timeout_seconds', 60, 86400),
            ('logs.retention_days', 1, 3650),
            ('logs.max_size_mb', 1, 10240)
        ]
        
        for key, min_val, max_val in numeric_checks:
            value = self.get(key)
            if value is not None:
                if not isinstance(value, (int, float)) or value < min_val or value > max_val:
                    errors.append(f"Invalid {key}: {value}. Must be between {min_val} and {max_val}")
        
        return errors
    
    def to_dict(self) -> Dict[str, Any]:
        """Return configuration as dictionary."""
        return self.data.copy()
    
    def __str__(self) -> str:
        """String representation of configuration."""
        return yaml.dump(self.data, default_flow_style=False)


# Global configuration instance
_global_config: Optional[SimpleConfig] = None


def get_config() -> SimpleConfig:
    """
    Get global configuration instance.
    
    Returns:
        Global SimpleConfig instance
    """
    global _global_config
    if _global_config is None:
        _global_config = SimpleConfig()
        
        # Load .env file first
        _global_config.load_env_file()
        
        # Try to load from default configuration file
        config_file = Path("config.yaml")
        if config_file.exists():
            _global_config.load_from_file(config_file)
        
        # Apply environment overrides
        _global_config.apply_environment_overrides()
        
        # Validate configuration
        errors = _global_config.validate()
        if errors:
            logger.warning(f"Configuration validation errors: {errors}")
    
    return _global_config


def load_config(config_path: Union[str, Path]) -> SimpleConfig:
    """
    Load configuration from specific file.
    
    Args:
        config_path: Path to configuration file
        
    Returns:
        Loaded SimpleConfig instance
    """
    config = SimpleConfig()
    config.load_from_file(config_path)
    config.apply_environment_overrides()
    
    errors = config.validate()
    if errors:
        logger.warning(f"Configuration validation errors: {errors}")
    
    return config


def init_config(config_data: Dict[str, Any]) -> SimpleConfig:
    """
    Initialize configuration with provided data.
    
    Args:
        config_data: Configuration dictionary
        
    Returns:
        Initialized SimpleConfig instance
    """
    global _global_config
    _global_config = SimpleConfig(config_data)
    _global_config.apply_environment_overrides()
    return _global_config


def create_default_config(output_path: Union[str, Path]) -> None:
    """
    Create default configuration file.
    
    Args:
        output_path: Path to save default configuration
    """
    config = SimpleConfig()
    config.save_to_file(output_path)
    logger.info(f"Default configuration created at {output_path}")